/*

 ARCHIVO: "main.cpp"

 Archivo principal del programa "tp0".
 Procesador de imágenes NetPBM en
 escala de grises y en colores.

 Primera versión: 1-4-2012

*/


#include <cstdlib>
#include <fstream>
#include <string>

#include "globals.h"
#include "cmdline.h"
#include "complex.h"
#include "image.h"


/*============================= Constantes del main -  Begin ============================= */

#define PROGRAM_NAME            "tp0"
#define PROGRAM_DESCRIPTION     "Netpbm image processor"
#define PROGRAM_VERSION         {0, 9, 0}

#define MIN_FUNCTION            1
#define MAX_FUNCTION            12

/*============================= Constantes del main -  End ============================= */


using namespace std;


/*---------------- Funciones de procesado de la línea de comando - Begin ----------------*/

// Procesa la opción de línea de comando "-i".
static void opt_istream(Parameters_Queue_t &args);

// Procesa la opción de línea de comando "-o".
static void opt_ostream(Parameters_Queue_t &args);

// Procesa la opción de línea de comando "-f".
static void opt_function(Parameters_Queue_t &args);

/*---------------- Funciones de procesado de la línea de comando - End ----------------*/


/*---------------- Funciones de transformación - Begin ----------------*/

// función cuadrado (z/10)**2 (implementa 10*sqrt).
static vector_complejos_t sqrt_2_scaled(const complejo &cmpn);

// función rotación pi/16 (implementa rotación -pi/16).
static vector_complejos_t rot_minus_pi_16(const complejo &cmpn);

// función rotación -pi/6 (implementa rotación pi/6).
static vector_complejos_t rot_pi_6(const complejo &cmpn);

// función rotación -pi/4 (implementa rotación pi/4).
static vector_complejos_t rot_pi_4(const complejo &cmpn);

// función rotación -pi/2 (implementa rotación pi/2).
static vector_complejos_t rot_pi_2(const complejo &cmpn);

// función identidad.
static vector_complejos_t ident(const complejo &cmpn);

// función que hace un flip horizontal.
static vector_complejos_t vert_flip(const complejo &cmpn);

// función que hace un flip vertical.
static vector_complejos_t horz_flip(const complejo &cmpn);

// función cuadrado sqrt (implementa z**2).
static vector_complejos_t pow_2(const complejo &cmpn);

// función pseudo_sqrt.
static vector_complejos_t pseudo_sqrt(const complejo &cmpn);

// función crazy1.
static vector_complejos_t crazy1(const complejo &cmpn);

// función crazy2.
static vector_complejos_t crazy2(const complejo &cmpn);
/*---------------- Funciones de transformación - End ----------------*/


// "Tabla" de funciones de transformación.
static transformation_function_t functions[MAX_FUNCTION] =
{
    sqrt_2_scaled, rot_minus_pi_16, rot_pi_2, rot_pi_4, rot_pi_6, ident,
    vert_flip, horz_flip, pow_2, pseudo_sqrt, crazy1, crazy2
};


// Puntero al stream de entrada.
static istream *iss = 0;
// Puntero al stream de salida.
static ostream *oss = 0;
// Archivo de entrada (stream).
static fstream ifs;
//Archivo de salida (stream).
static fstream ofs;
// Número de función de transformación solicitada.
static int function_number = 0;


/*---------------- Funciones de procesado de la línea de comando - Begin ----------------*/

// Procesa la opción de línea de comando "-i".
// Se espera que la misma retire los argumentos de la cola.
static void opt_istream(Parameters_Queue_t &args)
{
    // Se asegura que la cantidad
    // de argumentos en la cola
    // es de al menos la necesaria.

    // Tomo el único parámetro que
    // necesito ( nombre sel archivo).
    string arg = args.front();

    // Elimino de la cola
    // el parámtro consumido.
    args.pop();

    // Si el nombre del archivos es "-", usaremos la entrada
    // estándar. De lo contrario, abrimos un archivo en modo
    // de lectura.
    if (arg == "-")
    {
        iss = &cin;
    }
    else
    {
        ifs.open(arg.c_str(), ios::in);
        iss = &ifs;
    }

    // Verificamos que el stream este OK.
    if (!iss->good())
    {
        cerr << ERROR_CANNOT_OPEN
             << arg
             << DOT
             << endl;
        exit(ERROR_NUMBER_CANNOT_OPEN);
    }
}

// Procesa la opción de línea de comando "-o".
// Se espera que la misma retire los argumentos de la cola.
static void opt_ostream(Parameters_Queue_t &args)
{
    // Se asegura que la cantidad
    // de argumentos en la cola
    // es de al menos la necesaria.

    // Tomo el único parámetro que
    // necesito ( nombre sel archivo).
    string arg = args.front();

    // Elimino de la cola
    // el parámtro consumido.
    args.pop();

    // Si el nombre del archivos es "-", usaremos la salida
    // estándar. De lo contrario, abrimos un archivo en modo
    // de escritura.
    if (arg == "-")
    {
        oss = &cout;
    }
    else
    {
        ofs.open(arg.c_str(), ios::out);
        oss = &ofs;
    }

    // Verificamos que el stream este OK.
    if (!oss->good())
    {
        cerr << ERROR_CANNOT_OPEN
             << arg
             << DOT
             << endl;
        exit(ERROR_NUMBER_CANNOT_OPEN);
    }
}

// Procesa la opción de línea de comando "-f".
// Se espera que la misma retire los argumentos de la cola.
static void opt_function(Parameters_Queue_t &args)
{
    // Se asegura que la cantidad
    // de argumentos en la cola
    // es de al menos la necesaria.

    // Tomo el único parámetro que
    // necesito ( nombre sel archivo).
    string arg = args.front();

    // Elimino de la cola
    // el parámtro consumido.
    args.pop();

    std::istringstream argstream(arg);

    if (!(argstream >> function_number))
    {
        cerr << ERROR_INVALID_FUNCTION
             << arg
             << DOT
             << endl;
        exit(ERROR_NUMBER_INVALID_FUNCTION);
    }

    if ((function_number < MIN_FUNCTION) || (function_number > MAX_FUNCTION))
    {
        cerr << ERROR_UNKNOWN_FUNCTION
             << function_number
             << DOT
             << endl;
        exit(ERROR_NUMBER_UNKNOWN_FUNCTION);
    }
}

/*---------------- Funciones de procesado de la línea de comando - End ----------------*/


/*---------------- Funciones de transformación - Begin ----------------*/

// función cuadrado (z/10)**2 (implementa 10*sqrt).
static vector_complejos_t sqrt_2_scaled(const complejo &cmpn)
{
    // Vector de valores de la
    // imagen multivaluada.
    vector_complejos_t res;

    res.push_back(pow(cmpn/10, 2));

    return res;
}

// función rotación pi/16 (implementa rotación -pi/16).
static vector_complejos_t rot_minus_pi_16(const complejo &cmpn)
{
    // Vector de valores de la
    // imagen multivaluada.
    vector_complejos_t res;

    res.push_back(cmpn*complejo(1, M_PI/16, true));

    return res;
}

// función rotación -pi/6 (implementa rotación pi/6).
static vector_complejos_t rot_pi_6(const complejo &cmpn)
{
    // Vector de valores de la
    // imagen multivaluada.
    vector_complejos_t res;

    res.push_back(cmpn*complejo(1, -M_PI/6, true));

    return res;
}

// función rotación -pi/4 (implementa rotación pi/4).
static vector_complejos_t rot_pi_4(const complejo &cmpn)
{
    // Vector de valores de la
    // imagen multivaluada.
    vector_complejos_t res;

    res.push_back(cmpn*complejo(1, -M_PI/4, true));

    return res;
}

// función rotación -pi/2 (implementa rotación pi/2).
static vector_complejos_t rot_pi_2(const complejo &cmpn)
{
    // Vector de valores de la
    // imagen multivaluada.
    vector_complejos_t res;

    res.push_back(cmpn*complejo(0, -1));

    return res;
}

// función identidad.
static vector_complejos_t ident(const complejo &cmpn)
{
    // Vector de valores de la
    // imagen multivaluada.
    vector_complejos_t res;

    res.push_back(cmpn);

    return res;
}

// función que hace un flip horizontal.
static vector_complejos_t vert_flip(const complejo &cmpn)
{
    // Vector de valores de la
    // imagen multivaluada.
    vector_complejos_t res;

    res.push_back(cmpn.conjugado());

    return res;
}

// función que hace un flip vertical.
static vector_complejos_t horz_flip(const complejo &cmpn)
{
    // Vector de valores de la
    // imagen multivaluada.
    vector_complejos_t res;

    res.push_back(-cmpn.conjugado());

    return res;
}

// función sqrt (implementa z**2).
static vector_complejos_t pow_2(const complejo &cmpn)
{
    // Vector de valores de la
    // imagen multivaluada.
    vector_complejos_t res;

    res.push_back(pow(cmpn, 0.5));

    res.push_back(pow(cmpn, 0.5));

    return res;
}

// función pseudo_sqrt.
//Preserva el módulo, el ángulo es el doble.
static vector_complejos_t pseudo_sqrt(const complejo &cmpn)
{
    // Vector de valores de la
    // imagen multivaluada.
    vector_complejos_t res;

    res.push_back(complejo(cmpn.abs(), cmpn.arg() * 2, true));

    return res;
}

// función crazy1.
static vector_complejos_t crazy1(const complejo &cmpn)
{
    // Vector de valores de la
    // imagen multivaluada.
    vector_complejos_t res;

    res.push_back(complejo(cmpn.abs(), (cmpn.abs()/10)*cmpn.arg(), true));

    return res;
}

// función crazy2.
static vector_complejos_t crazy2(const complejo &cmpn)
{
    // Vector de valores de la
    // imagen multivaluada.
    vector_complejos_t res;

    //res.push_back(complejo(cmpn.abs(), (cmpn.abs()/10)*cmpn.arg(), true));

    res.push_back(cmpn);

    res.push_back(complejo(cmpn.abs(), cmpn.arg() + M_PI_2, true));

    res.push_back(complejo(cmpn.abs(), cmpn.arg() + M_PI, true));

    res.push_back(complejo(cmpn.abs(), cmpn.arg() + (3/2)*M_PI, true));

    return res;
}

/*---------------- Funciones de transformación - End ----------------*/

// Cuerpo del program.
int main(int argc, char * const argv[])
{
    // Program version.
    ProgramVersion Prog_version = PROGRAM_VERSION;

    // Objeto usado para parsear la línea de comando.
    CmdLine cmdl(PROGRAM_NAME, PROGRAM_DESCRIPTION, Prog_version, NULL);

    // Agrego las opciones usadas por el programa.
    cmdl.Add_Option(1, "i", "istream", "-",
                    HELP_TEXT_ISTREAM, opt_istream, false);

    cmdl.Add_Option(1, "o", "ostream", "-",
                    HELP_TEXT_OSTREAM, opt_ostream, false);

    cmdl.Add_Option(1, "f", "function", "1",
                    HELP_TEXT_FUNCTION, opt_function, false);
    //

    // Parseo la línea de comando.
    cmdl.Parse(argc, argv);

    // Fábrica de objetos imagen.
    NetPBM_Image_Factory Image_Factory;

    // Punteros a objetos imagen.
    NetPBM_Image *pimagen1 = NULL, *pimagen2 = NULL;

    // Creo un objeto imagen de la clase adecuada para
    // leer desde el stream de entrada.
    pimagen1 = Image_Factory.Create_Image_For_Stream(*iss);

    // Chequeo que se haya creado el objeto imagen.
    if (pimagen1 == NULL)
    {
        cerr << ERROR_BAD_STREAM << endl;

        return ERROR_NUMBER_BAD_STREAM;
    }

    // Creo un segundo objeto imagen del mismo tipo
    // que el primero.
    pimagen2 = Image_Factory.Create_Image_Same_Type(*pimagen1);

    // Indico el tipo de imagen que se está leyendo.
    cerr << READING << pimagen1->image_Name() << DOT << endl << endl;

    // Leo la imagen de origen del stream de entrada
    // y chequo por si hubo errores.
    if (!(*iss >> *pimagen1))
    {
        cerr << ERROR_BAD_IMAGE_FORMAT << endl;

        return ERROR_NUMBER_BAD_IMAGE_FORMAT;
    }

    // Indico que se esta procesando la imagen.
    cerr << PROCESSING << endl << endl;

    // Transformo la imagen con la función solicitada por línea de comando
    // y chequeo si hubo errores.
    if (pimagen1->transformation(*pimagen2, functions[function_number - 1]) != 0)
    {
        cerr << ERROR_TRANSFORMING_IMAGE;

        return ERROR_NUMBER_TRANSFORMING_IMAGE;
    }

    // Indico el tipo de imagen que se está escribiendo.
    cerr << WRITING << pimagen2->image_Name() << DOT << endl << endl;

    // Escribo la imagen transformada en el stream de salida
    // y chequeo si hubo errores.
    if (!(*oss << *pimagen2))
    {
        cerr << ERROR_WRITING_OUTPUT << endl;

        return ERROR_NUMBER_WRITING_OUTPUT;
    }

    // Indico que se terminó el proceso.
    cerr << COMPLETE << endl;

    // El proceso se completó correctamente.
    return ERROR_NUMBER_SUCCESS;
}
